package fun.ticsmyc.item.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import fun.ticsmyc.common.pojo.PageResult;
import fun.ticsmyc.item.dto.CommentAndUser;
import fun.ticsmyc.item.dto.CommentReplyAndUser;
import fun.ticsmyc.item.pojo.Comment;
import fun.ticsmyc.item.pojo.CommentReply;
import fun.ticsmyc.item.pojo.User;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 带Redis缓存的UserService
 *
 * @author Ticsmyc
 * @date 2020-05-27 21:07
 */
@Service
public class CommentRedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private CommentService commentService;

    @Autowired
    private UserService userService;

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private AmqpTemplate amqpTemplate;


    /**
     * Redis中key的前缀
     */
    private final String REDIS_COMMENT_PREFIX = "XDMALL:COMMENT";

    private final String REDIS_GOODS_COMMENT_PREFIX = "XDMALL:GOODS_COMMENTID:";

    /**
     * Redis中分布式锁的key
     */
    private final String REDIS_DISTRIBUTED_LOCKR = "XDMALL:COMMENT:SYN:LOCK";

    /**
     * Redis中保存的自增id
     */
    private final String REDIS_AUTOINCR_ID = "XDMALL:COMMENT_ID";

    /**
     * rabbit MQ 交换机的名称
     */
    private final String RABBITMQ_EXCHANGE_NAME="XDMALL.COMMENT.EXCHANGE";


    /**
     * 插入一条评论
     * 1. redis中如果没有该商品对应评论的缓存，就先加载缓存
     * 2. 在redis中新增评论
     * 3. 向消息队列发送新增评论的请求
     * @param comment
     */
    @Transactional(rollbackFor = Exception.class)
    public void insertComment(Comment comment){
        //1. 先根据comment对应的商品id ，去redis中查有没有这个商品的评论缓存

        if(!this.stringRedisTemplate.hasKey(REDIS_GOODS_COMMENT_PREFIX + comment.getGoodsId())){
            //从mysql中读取这个商品的评论，一条一条插入redis
            insertCommentFromMysqlToRedis(comment.getGoodsId());
        }

        // 2. 补全comment的信息
        Long id = stringRedisTemplate.opsForValue().increment(REDIS_AUTOINCR_ID,1);

        comment.setId(id);
        comment.setCreateTime(new Date());

        String commentJson = null;
        try {
            commentJson = mapper.writeValueAsString(comment);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        //3. 向redis中这个商品的记录添加comment
        this.stringRedisTemplate.opsForHash().put(REDIS_COMMENT_PREFIX,comment.getId()+"",commentJson);
        this.stringRedisTemplate.opsForZSet().add(REDIS_GOODS_COMMENT_PREFIX+comment.getGoodsId(),comment.getId()+"",comment.getId());

        //4. 向消息队列发送新增评论的消息
        Map<String,String> msg = new HashMap<>();
        msg.put("method","insert");
        msg.put("comment",commentJson);
        this.amqpTemplate.convertAndSend(RABBITMQ_EXCHANGE_NAME,"comment",msg);
    }


    /**
     * 根据商品id  分页查询评论
     * @param goodsId
     * @param page
     * @param row
     * @param replyTo
     * @return
     */
//    public PageResult<Comment> selectCommentsByPage(Long goodsId, Integer page, Integer row, Long replyTo) {
//        //先判断redis中有没有该商品的缓存，如果没有的话 先插入
//        if(! this.stringRedisTemplate.hasKey(REDIS_GOODS_COMMENT_PREFIX+goodsId)){
//            insertCommentFromMysqlToRedis(goodsId);
//        }
//        Long count = this.stringRedisTemplate.opsForZSet().count(REDIS_GOODS_COMMENT_PREFIX + goodsId,Long.MIN_VALUE,Long.MAX_VALUE);
//        Set<String> commentIds = this.stringRedisTemplate.opsForZSet().range(REDIS_GOODS_COMMENT_PREFIX + goodsId, (page - 1) * row, page * row-1);
//        List<Comment> comments = new ArrayList<>();
//
//        commentIds.forEach(commentId->{
//            String commentJson = (String)this.stringRedisTemplate.opsForHash().get(REDIS_COMMENT_PREFIX, commentId);
//            Comment comment = null;
//            try {
//                comment = mapper.readValue(commentJson,Comment.class);
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//            if(comment != null){
//                comments.add(comment);
//            }
//        });
//        return new PageResult<Comment>(comments,(int)(count%row==0 ? count/row:count/row+1),count);
//    }
    public PageResult<CommentAndUser> selectCommentsByPage(Long goodsId, Integer page, Integer row, Long replyTo) {
        //先判断redis中有没有该商品的缓存，如果没有的话 先插入
        if(! this.stringRedisTemplate.hasKey(REDIS_GOODS_COMMENT_PREFIX+goodsId)){
            insertCommentFromMysqlToRedis(goodsId);
        }
        Long count = this.stringRedisTemplate.opsForZSet().count(REDIS_GOODS_COMMENT_PREFIX + goodsId,Long.MIN_VALUE,Long.MAX_VALUE);
        Set<String> commentIds = this.stringRedisTemplate.opsForZSet().range(REDIS_GOODS_COMMENT_PREFIX + goodsId, (page - 1) * row, page * row-1);
        List<CommentAndUser> comments = new ArrayList<>();

        commentIds.forEach(commentId->{
            String commentJson = (String)this.stringRedisTemplate.opsForHash().get(REDIS_COMMENT_PREFIX, commentId);
            Comment comment = null;
            try {
                comment = mapper.readValue(commentJson,Comment.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if(comment != null){
                User user = userService.queryUserByid(comment.getUserId());
                CommentAndUser commentAndUser = new CommentAndUser();
                commentAndUser.setComment(comment);
                commentAndUser.setUser(user);
                comments.add(commentAndUser);
            }
        });
        return new PageResult<CommentAndUser>(comments,(int)(count%row==0 ? count/row:count/row+1),count);
    }

    /**
     * 修改评论
     * @param commentId
     * @param content
     */
    @Transactional(rollbackFor = Exception.class)
    public void modifyCommentById(Long commentId, String content) {
        //能进入到修改评论的功能，一定是先查到了这条评论，所以redis中一定有这条评论的数据
        String commentJson = (String)this.stringRedisTemplate.opsForHash().get(REDIS_COMMENT_PREFIX, commentId+"");
        // 修改redis中的数据
        try {
            Comment comment = mapper.readValue(commentJson,Comment.class);
            System.out.println("从redis中读到："+comment.toString());
            if(comment != null){
                comment.setContent(content);
                commentJson = mapper.writeValueAsString(comment);
                System.out.println("再转成json:"+commentJson);
                this.stringRedisTemplate.opsForHash().put(REDIS_COMMENT_PREFIX,commentId+"",commentJson);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


        // 给消息队列发消息，异步修改mysql中的数据
        Map<String,String> msg = new HashMap<>();
        msg.put("method","update");
        msg.put("comment",commentJson);
        this.amqpTemplate.convertAndSend(RABBITMQ_EXCHANGE_NAME,"comment",msg);

    }

    /**
     * 删除评论
     * @param commentId
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteCommentById(Long commentId) {
        //删除redis中数据
            //先读取这条评论的内容，拿到goodId
        Long goodId = null;
        String commentJson = (String)this.stringRedisTemplate.opsForHash().get(REDIS_COMMENT_PREFIX, commentId+"");
        try {
            Comment comment = mapper.readValue(commentJson,Comment.class);
            goodId=comment.getGoodsId();
        } catch (IOException e) {
            e.printStackTrace();
        }
            //删除hash
        this.stringRedisTemplate.opsForHash().delete(REDIS_COMMENT_PREFIX, commentId + "");
            //删除ZSet
        this.stringRedisTemplate.opsForZSet().removeRangeByScore(REDIS_GOODS_COMMENT_PREFIX+goodId,commentId,commentId);

        // 给消息队列发消息，异步删除mysql的数据
        Map<String,String> msg = new HashMap<>();
        msg.put("method","delete");
        msg.put("commentId",commentId+"");
        this.amqpTemplate.convertAndSend(RABBITMQ_EXCHANGE_NAME,"comment",msg);
    }


    /**
     * 根据商品id，从mysql中读取评论信息，插入redis中做为缓存
     * @param goodId
     */
    @Transactional(rollbackFor = Exception.class)
    public void insertCommentFromMysqlToRedis(Long goodId){

        RLock disLock = redissonClient.getLock(REDIS_DISTRIBUTED_LOCKR);
        try {
            disLock.lock(15000, TimeUnit.MILLISECONDS);

            if(this.stringRedisTemplate.hasKey(REDIS_GOODS_COMMENT_PREFIX + goodId)){
                // 双重判断 ：已经有数据了就不重复导入
                return ;
            }
            //从mysql中读取这个商品的评论，一条一条插入redis
            for(int i=1;;++i) {
                PageResult<Comment> commentPageResult = this.commentService.selectCommentsByPage(goodId, i,50);
                commentPageResult.getItems().forEach(one -> {
                    try {
                        String commentJson = mapper.writeValueAsString(one);
                        //hash 存【 评论id ： 评论内容】
                        this.stringRedisTemplate.opsForHash().put(REDIS_COMMENT_PREFIX,one.getId()+"",commentJson);
                        //zset存 【商品id ： 评论id (score是评论id [按时间顺序排序])】
                        this.stringRedisTemplate.opsForZSet().add(REDIS_GOODS_COMMENT_PREFIX+goodId,one.getId()+"",one.getId());
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                    }
                });
                if(commentPageResult.getTotalPage() <= i ){
                    break;
                }
            }
        } finally {
            disLock.unlock();
        }
    }

    /**
     * 回复评论
     * @param commentReply
     */
    public void insertReplyComment(CommentReply commentReply) {

        //补全commentReply的信息
        Long id = stringRedisTemplate.opsForValue().increment(REDIS_AUTOINCR_ID,1);
        commentReply.setId(id);
        commentReply.setCreate_time(new Date());

        this.commentService.insertReplyComment(commentReply);

    }

    /**
     * 删除回复
     * @param replyId
     */
    public void deleteCommentReplyById(Long replyId) {
        //删redis
        //删mysql
        this.commentService.deleteCommentReplyById(replyId);
    }

    /**
     * 根据评论id 分页查询这条评论的回复
     * @param commentId
     * @param page
     * @param row
     * @return
     */
    public PageResult<CommentReplyAndUser> selectCommentsReplyByPage(Long commentId, Integer page, Integer row) {
        return this.commentService.selectCommentsReplyByPage(commentId,page,row);
    }
}
